Design Instagram
Table of Contents
- Overview
- System Requirements
- Class Design
- Database Schema
- API Design
- Key Algorithms
- Design Patterns
- Error Handling
- Performance Optimizations
Overview
This document provides the Low-Level Design (LLD) for Instagram, focusing on detailed class structures, data models, and implementation details for core features including posts, stories, feed generation, comments, likes, and user interactions.
System Requirements
Functional Requirements
- User registration and authentication
- Create, view, edit, and delete posts
- Upload and manage photos/videos
- Create and view stories (24-hour expiration)
- Like and comment on posts
- Follow/unfollow users
- View personalized feed
- Search users and posts
- Direct messaging
- Notifications
Non-Functional Requirements
- High availability (99.9% uptime)
- Low latency (< 200ms for feed generation)
- Scalability (billions of users)
- Consistency for critical operations
- Eventual consistency acceptable for some features
Class Design
Core Classes
User
class User {
constructor(userId, username, email, passwordHash) {
this.userId = userId;
this.username = username;
this.email = email;
this.passwordHash = passwordHash;
this.profilePicture = null;
this.bio = '';
this.isPrivate = false;
this.createdAt = new Date();
this.updatedAt = new Date();
}
updateProfile(profileData) {
// Update user profile information
}
changePassword(newPasswordHash) {
// Update password
}
togglePrivacy() {
// Toggle account privacy
}
}
Post
class Post {
constructor(postId, userId, mediaUrl, caption) {
this.postId = postId;
this.userId = userId;
this.mediaUrl = mediaUrl;
this.caption = caption;
this.likesCount = 0;
this.commentsCount = 0;
this.createdAt = new Date();
this.location = null;
this.tags = [];
}
addLike(userId) {
// Increment likes count
}
removeLike(userId) {
// Decrement likes count
}
addComment(comment) {
// Add comment and increment count
}
deleteComment(commentId) {
// Remove comment and decrement count
}
}
Story
class Story {
constructor(storyId, userId, mediaUrl, type) {
this.storyId = storyId;
this.userId = userId;
this.mediaUrl = mediaUrl;
this.type = type; // 'photo' or 'video'
this.createdAt = new Date();
this.expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
this.viewers = new Set();
}
addViewer(userId) {
// Add user to viewers list
}
isExpired() {
return new Date() > this.expiresAt;
}
getViewersCount() {
return this.viewers.size;
}
}
Comment
class Comment {
constructor(commentId, postId, userId, content) {
this.commentId = commentId;
this.postId = postId;
this.userId = userId;
this.content = content;
this.likesCount = 0;
this.createdAt = new Date();
this.parentCommentId = null; // For nested comments
}
addLike(userId) {
// Increment likes count
}
removeLike(userId) {
// Decrement likes count
}
isReply() {
return this.parentCommentId !== null;
}
}
Like
class Like {
constructor(likeId, userId, targetId, targetType) {
this.likeId = likeId;
this.userId = userId;
this.targetId = targetId; // postId or commentId
this.targetType = targetType; // 'post' or 'comment'
this.createdAt = new Date();
}
}
Follow
class Follow {
constructor(followId, followerId, followingId) {
this.followId = followId;
this.followerId = followerId;
this.followingId = followingId;
this.createdAt = new Date();
this.status = 'active'; // 'active' or 'blocked'
}
block() {
this.status = 'blocked';
}
unblock() {
this.status = 'active';
}
}
Service Classes
PostService
class PostService {
constructor(postRepository, mediaService, notificationService) {
this.postRepository = postRepository;
this.mediaService = mediaService;
this.notificationService = notificationService;
}
async createPost(userId, mediaFile, caption, location) {
// 1. Upload media to storage
const mediaUrl = await this.mediaService.upload(mediaFile);
// 2. Create post record
const post = new Post(generateId(), userId, mediaUrl, caption);
if (location) post.location = location;
// 3. Extract and save hashtags
const hashtags = this.extractHashtags(caption);
await this.saveHashtags(post.postId, hashtags);
// 4. Save post
await this.postRepository.save(post);
// 5. Notify followers
await this.notificationService.notifyNewPost(userId, post.postId);
return post;
}
async getPost(postId, userId) {
const post = await this.postRepository.findById(postId);
if (!post) throw new Error('Post not found');
// Check if user has permission to view
const canView = await this.canUserViewPost(userId, post);
if (!canView) throw new Error('Access denied');
return post;
}
async deletePost(postId, userId) {
const post = await this.postRepository.findById(postId);
if (post.userId !== userId) {
throw new Error('Unauthorized');
}
// Delete media from storage
await this.mediaService.delete(post.mediaUrl);
// Delete post and related data
await this.postRepository.delete(postId);
}
extractHashtags(caption) {
const hashtagRegex = /#(\w+)/g;
return caption.match(hashtagRegex) || [];
}
async canUserViewPost(userId, post) {
const postOwner = await this.userRepository.findById(post.userId);
// Public account or own post
if (!postOwner.isPrivate || post.userId === userId) {
return true;
}
// Check if user follows the post owner
return await this.followRepository.isFollowing(userId, post.userId);
}
}
FeedService
class FeedService {
constructor(
postRepository,
followRepository,
cacheService,
feedGenerator
) {
this.postRepository = postRepository;
this.followRepository = followRepository;
this.cacheService = cacheService;
this.feedGenerator = feedGenerator;
}
async getFeed(userId, page = 1, pageSize = 10) {
// Check cache first
const cacheKey = `feed:${userId}:${page}`;
const cachedFeed = await this.cacheService.get(cacheKey);
if (cachedFeed) {
return cachedFeed;
}
// Get following list
const following = await this.followRepository.getFollowing(userId);
const followingIds = following.map(f => f.followingId);
// Generate feed using ranking algorithm
const feed = await this.feedGenerator.generateFeed(
userId,
followingIds,
page,
pageSize
);
// Cache the result
await this.cacheService.set(cacheKey, feed, 300); // 5 minutes TTL
return feed;
}
async refreshFeed(userId) {
// Invalidate cache
await this.cacheService.deletePattern(`feed:${userId}:*`);
// Trigger background job to regenerate feed
await this.feedGenerator.precomputeFeed(userId);
}
}
FeedGenerator
class FeedGenerator {
constructor(postRepository, likeRepository, commentRepository) {
this.postRepository = postRepository;
this.likeRepository = likeRepository;
this.commentRepository = commentRepository;
}
async generateFeed(userId, followingIds, page, pageSize) {
if (followingIds.length === 0) {
return this.getSuggestedPosts(userId, pageSize);
}
// Get recent posts from following
const posts = await this.postRepository.findByUserIds(
followingIds,
page,
pageSize
);
// Rank posts using scoring algorithm
const rankedPosts = await this.rankPosts(posts, userId);
return rankedPosts;
}
async rankPosts(posts, userId) {
// Score each post based on multiple factors
const scoredPosts = await Promise.all(
posts.map(async (post) => {
const score = await this.calculatePostScore(post, userId);
return { post, score };
})
);
// Sort by score (descending)
scoredPosts.sort((a, b) => b.score - a.score);
return scoredPosts.map(item => item.post);
}
async calculatePostScore(post, userId) {
const weights = {
recency: 0.3,
likes: 0.25,
comments: 0.2,
userEngagement: 0.15,
relationship: 0.1
};
// Recency score (exponential decay)
const hoursSincePost = (Date.now() - post.createdAt) / (1000 * 60 * 60);
const recencyScore = Math.exp(-hoursSincePost / 24);
// Engagement score
const likesScore = Math.log(1 + post.likesCount) / 10;
const commentsScore = Math.log(1 + post.commentsCount) / 10;
// User engagement with post owner
const userEngagement = await this.getUserEngagementScore(
userId,
post.userId
);
// Relationship score (close friends, frequent interactions)
const relationshipScore = await this.getRelationshipScore(
userId,
post.userId
);
return (
weights.recency * recencyScore +
weights.likes * likesScore +
weights.comments * commentsScore +
weights.userEngagement * userEngagement +
weights.relationship * relationshipScore
);
}
async getUserEngagementScore(userId, postOwnerId) {
// Calculate based on user's past interactions with post owner
const interactions = await this.getInteractionHistory(
userId,
postOwnerId
);
return Math.min(interactions / 10, 1); // Normalize to 0-1
}
async getRelationshipScore(userId, postOwnerId) {
// Check if user is in post owner's close friends list
// Check interaction frequency
// Return normalized score
return 0.5; // Placeholder
}
}
StoryService
class StoryService {
constructor(
storyRepository,
mediaService,
notificationService,
scheduler
) {
this.storyRepository = storyRepository;
this.mediaService = mediaService;
this.notificationService = notificationService;
this.scheduler = scheduler;
}
async createStory(userId, mediaFile, type) {
// Upload media
const mediaUrl = await this.mediaService.upload(mediaFile);
// Create story
const story = new Story(generateId(), userId, mediaUrl, type);
await this.storyRepository.save(story);
// Schedule deletion after 24 hours
this.scheduler.schedule(
story.expiresAt,
() => this.deleteStory(story.storyId)
);
// Notify followers
await this.notificationService.notifyNewStory(userId, story.storyId);
return story;
}
async viewStory(storyId, userId) {
const story = await this.storyRepository.findById(storyId);
if (!story || story.isExpired()) {
throw new Error('Story not found or expired');
}
// Check if user can view story
const canView = await this.canUserViewStory(userId, story);
if (!canView) {
throw new Error('Access denied');
}
// Record view
story.addViewer(userId);
await this.storyRepository.update(story);
return story;
}
async getStories(userId) {
// Get stories from users that userId follows
const following = await this.followRepository.getFollowing(userId);
const followingIds = following.map(f => f.followingId);
const stories = await this.storyRepository.findByUserIds(followingIds);
// Group by user
return this.groupStoriesByUser(stories);
}
async deleteStory(storyId) {
const story = await this.storyRepository.findById(storyId);
if (story) {
await this.mediaService.delete(story.mediaUrl);
await this.storyRepository.delete(storyId);
}
}
async cleanupExpiredStories() {
// Background job to clean up expired stories
const expiredStories = await this.storyRepository.findExpired();
for (const story of expiredStories) {
await this.deleteStory(story.storyId);
}
}
}
LikeService
class LikeService {
constructor(likeRepository, postRepository, notificationService) {
this.likeRepository = likeRepository;
this.postRepository = postRepository;
this.notificationService = notificationService;
}
async likePost(userId, postId) {
// Check if already liked
const existingLike = await this.likeRepository.findByUserAndTarget(
userId,
postId,
'post'
);
if (existingLike) {
throw new Error('Already liked');
}
// Create like
const like = new Like(generateId(), userId, postId, 'post');
await this.likeRepository.save(like);
// Update post likes count
const post = await this.postRepository.findById(postId);
post.addLike(userId);
await this.postRepository.update(post);
// Notify post owner
if (post.userId !== userId) {
await this.notificationService.notifyLike(post.userId, userId, postId);
}
return like;
}
async unlikePost(userId, postId) {
const like = await this.likeRepository.findByUserAndTarget(
userId,
postId,
'post'
);
if (!like) {
throw new Error('Like not found');
}
// Delete like
await this.likeRepository.delete(like.likeId);
// Update post likes count
const post = await this.postRepository.findById(postId);
post.removeLike(userId);
await this.postRepository.update(post);
}
async getLikes(postId, page = 1, pageSize = 20) {
return await this.likeRepository.findByTarget(postId, 'post', page, pageSize);
}
}
CommentService
class CommentService {
constructor(
commentRepository,
postRepository,
notificationService
) {
this.commentRepository = commentRepository;
this.postRepository = postRepository;
this.notificationService = notificationService;
}
async addComment(userId, postId, content, parentCommentId = null) {
const comment = new Comment(
generateId(),
postId,
userId,
content
);
if (parentCommentId) {
comment.parentCommentId = parentCommentId;
}
await this.commentRepository.save(comment);
// Update post comments count
const post = await this.postRepository.findById(postId);
post.addComment(comment);
await this.postRepository.update(post);
// Notify post owner and parent comment owner
if (post.userId !== userId) {
await this.notificationService.notifyComment(
post.userId,
userId,
postId,
comment.commentId
);
}
if (parentCommentId) {
const parentComment = await this.commentRepository.findById(parentCommentId);
if (parentComment && parentComment.userId !== userId) {
await this.notificationService.notifyReply(
parentComment.userId,
userId,
postId,
comment.commentId
);
}
}
return comment;
}
async deleteComment(commentId, userId) {
const comment = await this.commentRepository.findById(commentId);
if (!comment) {
throw new Error('Comment not found');
}
// Check authorization
const post = await this.postRepository.findById(comment.postId);
if (comment.userId !== userId && post.userId !== userId) {
throw new Error('Unauthorized');
}
await this.commentRepository.delete(commentId);
// Update post comments count
post.deleteComment(commentId);
await this.postRepository.update(post);
}
async getComments(postId, page = 1, pageSize = 20) {
return await this.commentRepository.findByPostId(postId, page, pageSize);
}
}
FollowService
class FollowService {
constructor(
followRepository,
userRepository,
notificationService
) {
this.followRepository = followRepository;
this.userRepository = userRepository;
this.notificationService = notificationService;
}
async followUser(followerId, followingId) {
if (followerId === followingId) {
throw new Error('Cannot follow yourself');
}
// Check if already following
const existingFollow = await this.followRepository.findByFollowerAndFollowing(
followerId,
followingId
);
if (existingFollow && existingFollow.status === 'active') {
throw new Error('Already following');
}
const followingUser = await this.userRepository.findById(followingId);
// Check if account is private
if (followingUser.isPrivate) {
// Create follow request instead
return await this.createFollowRequest(followerId, followingId);
}
// Create follow relationship
const follow = new Follow(generateId(), followerId, followingId);
await this.followRepository.save(follow);
// Notify user
await this.notificationService.notifyFollow(followingId, followerId);
return follow;
}
async unfollowUser(followerId, followingId) {
const follow = await this.followRepository.findByFollowerAndFollowing(
followerId,
followingId
);
if (!follow) {
throw new Error('Not following');
}
await this.followRepository.delete(follow.followId);
}
async getFollowers(userId, page = 1, pageSize = 20) {
return await this.followRepository.findByFollowing(userId, page, pageSize);
}
async getFollowing(userId, page = 1, pageSize = 20) {
return await this.followRepository.findByFollower(userId, page, pageSize);
}
}
SearchService
class SearchService {
constructor(
userRepository,
postRepository,
searchIndex
) {
this.userRepository = userRepository;
this.postRepository = postRepository;
this.searchIndex = searchIndex;
}
async searchUsers(query, page = 1, pageSize = 20) {
// Use search index for fast lookup
const userIds = await this.searchIndex.searchUsers(query, page, pageSize);
// Fetch user details
const users = await Promise.all(
userIds.map(id => this.userRepository.findById(id))
);
// Rank results (verified accounts, followers count, etc.)
return this.rankUsers(users);
}
async searchPosts(query, page = 1, pageSize = 20) {
// Search by hashtags, captions, locations
const postIds = await this.searchIndex.searchPosts(query, page, pageSize);
const posts = await Promise.all(
postIds.map(id => this.postRepository.findById(id))
);
return this.rankPosts(posts);
}
rankUsers(users) {
// Sort by verification status, followers count, relevance
return users.sort((a, b) => {
if (a.isVerified !== b.isVerified) {
return b.isVerified - a.isVerified;
}
return b.followersCount - a.followersCount;
});
}
rankPosts(posts) {
// Sort by engagement, recency
return posts.sort((a, b) => {
const scoreA = a.likesCount + a.commentsCount;
const scoreB = b.likesCount + b.commentsCount;
return scoreB - scoreA;
});
}
}
Database Schema
Users Table
CREATE TABLE users (
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
profile_picture_url VARCHAR(500),
bio TEXT,
is_private BOOLEAN DEFAULT FALSE,
is_verified BOOLEAN DEFAULT FALSE,
followers_count INT DEFAULT 0,
following_count INT DEFAULT 0,
posts_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_email (email)
);
Posts Table
CREATE TABLE posts (
post_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
media_url VARCHAR(500) NOT NULL,
media_type VARCHAR(20) NOT NULL, -- 'photo' or 'video'
caption TEXT,
location VARCHAR(200),
likes_count INT DEFAULT 0,
comments_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at),
INDEX idx_location (location)
);
Stories Table
CREATE TABLE stories (
story_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
media_url VARCHAR(500) NOT NULL,
media_type VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
viewers_count INT DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(user_id),
INDEX idx_user_id (user_id),
INDEX idx_expires_at (expires_at)
);
Story Views Table
CREATE TABLE story_views (
view_id BIGINT PRIMARY KEY,
story_id BIGINT NOT NULL,
viewer_id BIGINT NOT NULL,
viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (story_id) REFERENCES stories(story_id),
FOREIGN KEY (viewer_id) REFERENCES users(user_id),
UNIQUE KEY unique_story_viewer (story_id, viewer_id),
INDEX idx_story_id (story_id)
);
Comments Table
CREATE TABLE comments (
comment_id BIGINT PRIMARY KEY,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
parent_comment_id BIGINT,
likes_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(post_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (parent_comment_id) REFERENCES comments(comment_id),
INDEX idx_post_id (post_id),
INDEX idx_user_id (user_id),
INDEX idx_parent_comment_id (parent_comment_id)
);
Likes Table
CREATE TABLE likes (
like_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
target_id BIGINT NOT NULL,
target_type VARCHAR(20) NOT NULL, -- 'post' or 'comment'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
UNIQUE KEY unique_user_target (user_id, target_id, target_type),
INDEX idx_target (target_id, target_type),
INDEX idx_user_id (user_id)
);
Follows Table
CREATE TABLE follows (
follow_id BIGINT PRIMARY KEY,
follower_id BIGINT NOT NULL,
following_id BIGINT NOT NULL,
status VARCHAR(20) DEFAULT 'active', -- 'active' or 'blocked'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (follower_id) REFERENCES users(user_id),
FOREIGN KEY (following_id) REFERENCES users(user_id),
UNIQUE KEY unique_follow (follower_id, following_id),
INDEX idx_follower_id (follower_id),
INDEX idx_following_id (following_id)
);
Hashtags Table
CREATE TABLE hashtags (
hashtag_id BIGINT PRIMARY KEY,
tag VARCHAR(100) UNIQUE NOT NULL,
posts_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_tag (tag)
);
Post Hashtags Table
CREATE TABLE post_hashtags (
post_id BIGINT NOT NULL,
hashtag_id BIGINT NOT NULL,
PRIMARY KEY (post_id, hashtag_id),
FOREIGN KEY (post_id) REFERENCES posts(post_id),
FOREIGN KEY (hashtag_id) REFERENCES hashtags(hashtag_id),
INDEX idx_hashtag_id (hashtag_id)
);
Notifications Table
CREATE TABLE notifications (
notification_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
type VARCHAR(50) NOT NULL, -- 'like', 'comment', 'follow', etc.
actor_id BIGINT NOT NULL,
target_id BIGINT, -- post_id, comment_id, etc.
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (actor_id) REFERENCES users(user_id),
INDEX idx_user_id (user_id),
INDEX idx_is_read (is_read),
INDEX idx_created_at (created_at)
);
API Design
RESTful Endpoints
User Endpoints
POST /api/v1/users/register
POST /api/v1/users/login
GET /api/v1/users/:userId
PUT /api/v1/users/:userId
DELETE /api/v1/users/:userId
GET /api/v1/users/:userId/posts
GET /api/v1/users/:userId/followers
GET /api/v1/users/:userId/following
Post Endpoints
POST /api/v1/posts
GET /api/v1/posts/:postId
PUT /api/v1/posts/:postId
DELETE /api/v1/posts/:postId
GET /api/v1/posts/:postId/likes
GET /api/v1/posts/:postId/comments
Story Endpoints
POST /api/v1/stories
GET /api/v1/stories
GET /api/v1/stories/:storyId
POST /api/v1/stories/:storyId/view
DELETE /api/v1/stories/:storyId
Feed Endpoints
GET /api/v1/feed
POST /api/v1/feed/refresh
Like Endpoints
POST /api/v1/posts/:postId/like
DELETE /api/v1/posts/:postId/like
POST /api/v1/comments/:commentId/like
DELETE /api/v1/comments/:commentId/like
Comment Endpoints
POST /api/v1/posts/:postId/comments
DELETE /api/v1/comments/:commentId
PUT /api/v1/comments/:commentId
Follow Endpoints
POST /api/v1/users/:userId/follow
DELETE /api/v1/users/:userId/follow
Search Endpoints
GET /api/v1/search/users?q=query
GET /api/v1/search/posts?q=query
GET /api/v1/search/hashtags?q=query
Request/Response Examples
Create Post
POST /api/v1/posts
Content-Type: multipart/form-data
{
"media": <file>,
"caption": "Beautiful sunset! #sunset #nature",
"location": "San Francisco, CA"
}
Response: 201 Created
{
"postId": "123456789",
"userId": "987654321",
"mediaUrl": "https://cdn.instagram.com/media/...",
"caption": "Beautiful sunset! #sunset #nature",
"createdAt": "2024-01-15T10:30:00Z"
}
Get Feed
GET /api/v1/feed?page=1&pageSize=10
Response: 200 OK
{
"posts": [
{
"postId": "123456789",
"userId": "987654321",
"username": "johndoe",
"profilePicture": "https://...",
"mediaUrl": "https://...",
"caption": "Beautiful sunset!",
"likesCount": 150,
"commentsCount": 25,
"isLiked": true,
"createdAt": "2024-01-15T10:30:00Z"
}
],
"hasMore": true,
"nextPage": 2
}
Key Algorithms
Feed Ranking Algorithm
The feed ranking algorithm uses a scoring system based on multiple factors:
function calculatePostScore(post, user) {
const scores = {
recency: calculateRecencyScore(post.createdAt),
engagement: calculateEngagementScore(post),
relationship: calculateRelationshipScore(user, post.userId),
contentQuality: calculateContentQualityScore(post)
};
const weights = {
recency: 0.3,
engagement: 0.35,
relationship: 0.2,
contentQuality: 0.15
};
return (
scores.recency * weights.recency +
scores.engagement * weights.engagement +
scores.relationship * weights.relationship +
scores.contentQuality * weights.contentQuality
);
}
function calculateRecencyScore(createdAt) {
const hoursSincePost = (Date.now() - createdAt) / (1000 * 60 * 60);
// Exponential decay: newer posts score higher
return Math.exp(-hoursSincePost / 24);
}
function calculateEngagementScore(post) {
const likesScore = Math.log(1 + post.likesCount) / 10;
const commentsScore = Math.log(1 + post.commentsCount) / 10;
return (likesScore + commentsScore) / 2;
}
Story Expiration Algorithm
Stories automatically expire after 24 hours:
class StoryExpirationScheduler {
constructor(storyRepository) {
this.storyRepository = storyRepository;
this.startScheduler();
}
startScheduler() {
// Run every hour
setInterval(() => {
this.cleanupExpiredStories();
}, 60 * 60 * 1000);
}
async cleanupExpiredStories() {
const expiredStories = await this.storyRepository.findExpired();
for (const story of expiredStories) {
await this.deleteStory(story.storyId);
}
}
}
Design Patterns
Repository Pattern
Used for data access abstraction:
class PostRepository {
async save(post) { /* ... */ }
async findById(postId) { /* ... */ }
async findByUserId(userId) { /* ... */ }
async delete(postId) { /* ... */ }
}
Service Layer Pattern
Business logic separated into service classes:
- PostService
- FeedService
- StoryService
- LikeService
- CommentService
Observer Pattern
For notifications:
class NotificationService {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(event) {
this.observers.forEach(observer => observer.update(event));
}
}
Factory Pattern
For creating different types of media:
class MediaFactory {
createMedia(type, file) {
switch(type) {
case 'photo':
return new Photo(file);
case 'video':
return new Video(file);
default:
throw new Error('Unsupported media type');
}
}
}
Strategy Pattern
For different feed ranking strategies:
class FeedRankingStrategy {
rank(posts) {
throw new Error('Must implement rank method');
}
}
class EngagementBasedRanking extends FeedRankingStrategy {
rank(posts) {
return posts.sort((a, b) =>
(b.likesCount + b.commentsCount) - (a.likesCount + a.commentsCount)
);
}
}
Error Handling
Error Types
class InstagramError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.name = 'InstagramError';
}
}
class NotFoundError extends InstagramError {
constructor(resource) {
super(`${resource} not found`, 404);
this.name = 'NotFoundError';
}
}
class UnauthorizedError extends InstagramError {
constructor(message = 'Unauthorized') {
super(message, 401);
this.name = 'UnauthorizedError';
}
}
class ValidationError extends InstagramError {
constructor(message) {
super(message, 400);
this.name = 'ValidationError';
}
}
Error Handling Middleware
function errorHandler(err, req, res, next) {
if (err instanceof InstagramError) {
return res.status(err.statusCode).json({
error: {
message: err.message,
type: err.name
}
});
}
// Default error
res.status(500).json({
error: {
message: 'Internal server error',
type: 'InternalServerError'
}
});
}
Performance Optimizations
Caching Strategy
- Feed Cache: Cache user feeds for 5 minutes
- User Profile Cache: Cache user profiles for 15 minutes
- Post Cache: Cache popular posts for 1 hour
- Story Cache: Cache active stories for 10 minutes
Database Optimizations
- Indexes on frequently queried columns
- Partitioning for large tables (posts, likes)
- Read replicas for read-heavy operations
- Connection pooling
Lazy Loading
- Load comments on demand (pagination)
- Load likes list on demand
- Load followers/following lists with pagination
CDN for Media
- All media files served through CDN
- Image compression and multiple resolutions
- Video transcoding for different qualities
Background Jobs
- Feed precomputation
- Story expiration cleanup
- Notification delivery
- Analytics processing
Conclusion
This LLD provides a comprehensive design for Instagram's core features, focusing on:
- Clean class structure and separation of concerns
- Scalable database schema
- Efficient feed ranking algorithm
- Proper error handling
- Performance optimizations
The design follows best practices and design patterns to ensure maintainability and scalability.